'use strict';

var testHelper = require('../support/test-helper');

var assert = require('../support/assert');

var _ = require('underscore');

var LayergroupToken = require('../../lib/models/layergroup-token');

var PgQueryRunner = require('../../lib/backends/pg-query-runner');
var QueryTables = require('cartodb-query-tables').queryTables;
const createServer = require('../../lib/server');
var serverOptions = require('../../lib/server-options');

describe('tests from old api translated to multilayer', function () {
    var server;

    before(function () {
        server = createServer(serverOptions);
        server.setMaxListeners(0);
    });

    var layergroupUrl = '/api/v1/map';

    var keysToDelete;

    beforeEach(function () {
        keysToDelete = {};
    });

    afterEach(function (done) {
        testHelper.deleteRedisKeys(keysToDelete, done);
    });

    var wadusSql = 'select 1 as cartodb_id, null::geometry as the_geom_webmercator';
    var pointSql = "SELECT 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator, 1::int as cartodb_id";

    function singleLayergroupConfig (sql, cartocss) {
        return {
            version: '1.0.0',
            layers: [
                {
                    type: 'mapnik',
                    options: {
                        sql: sql,
                        cartocss: cartocss,
                        cartocss_version: '2.0.1'
                    }
                }
            ]
        };
    }

    function createRequest (layergroup, userHost, apiKey) {
        var url = layergroupUrl;
        if (apiKey) {
            url += '?api_key=' + apiKey;
        }
        return {
            url: url,
            method: 'POST',
            headers: {
                host: userHost || 'localhost',
                'Content-Type': 'application/json'
            },
            data: JSON.stringify(layergroup)
        };
    }

    it('layergroup creation fails if CartoCSS is bogus', function (done) {
        var layergroup = singleLayergroupConfig(wadusSql, '#my_table3{');
        assert.response(server,
            createRequest(layergroup),
            {
                status: 400
            },
            function (res) {
                var parsed = JSON.parse(res.body);
                assert.ok(parsed.errors[0].match(/^style0/));
                assert.ok(parsed.errors[0].match(/missing closing/));
                done();
            }
        );
    });

    it('multiple bad styles returns 400 with all errors', function (done) {
        var layergroup = singleLayergroupConfig(wadusSql, '#my_table4{backgxxxxxround-color:#fff;foo:bar}');
        assert.response(server,
            createRequest(layergroup),
            {
                status: 400
            },
            function (res) {
                var parsed = JSON.parse(res.body);
                assert.strictEqual(parsed.errors.length, 1);
                assert.ok(parsed.errors[0].match(/^style0/));
                assert.ok(parsed.errors[0].match(/Unrecognized rule: backgxxxxxround-color/));
                assert.ok(parsed.errors[0].match(/Unrecognized rule: foo/));
                done();
            }
        );
    });

    // Zoom is a special variable
    it("Specifying zoom level in CartoCSS does not need a 'zoom' variable in SQL output", function (done) {
        var layergroup = singleLayergroupConfig(pointSql, '#gadm4 [ zoom>=3] { marker-fill:red; }');

        assert.response(server,
            createRequest(layergroup),
            {
                status: 200
            },
            function (res) {
                var parsed = JSON.parse(res.body);
                assert.ok(parsed.layergroupid);
                assert.strictEqual(res.headers['x-layergroup-id'], parsed.layergroupid);

                keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
                keysToDelete['user:localhost:mapviews:global'] = 5;

                done();
            }
        );
    });

    // See https://github.com/CartoDB/Windshaft-cartodb/issues/88
    it('getting a tile from a user-specific database should return an expected tile', function (done) {
        var layergroup = singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }');

        var backupDBHost = global.environment.postgres.host;
        global.environment.postgres.host = '6.6.6.6';

        assert.response(server,
            createRequest(layergroup, 'cartodb250user'),
            {
                status: 200
            },
            function (res) {
                var parsed = JSON.parse(res.body);
                assert.ok(parsed.layergroupid);
                assert.strictEqual(res.headers['x-layergroup-id'], parsed.layergroupid);

                keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
                keysToDelete['user:cartodb250user:mapviews:global'] = 5;

                global.environment.postgres.host = backupDBHost;
                done();
            }
        );
    });

    // See https://github.com/CartoDB/Windshaft-cartodb/issues/89
    it('getting a tile with a user-specific database password', function (done) {
        var layergroup = singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }');

        var backupDBPass = global.environment.postgres_auth_pass;
        global.environment.postgres_auth_pass = '<%= user_password %>';

        assert.response(server,
            createRequest(layergroup, 'cartodb250user', '4321'),
            {
                status: 200
            },
            function (res) {
                var parsed = JSON.parse(res.body);
                assert.ok(parsed.layergroupid);
                assert.strictEqual(res.headers['x-layergroup-id'], parsed.layergroupid);

                keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
                keysToDelete['user:cartodb250user:mapviews:global'] = 5;

                global.environment.postgres_auth_pass = backupDBPass;
                done();
            }
        );
    });

    it('creating a layergroup from lzma param', function (done) {
        var params = {
            config: JSON.stringify(singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }'))
        };

        testHelper.lzma_compress_to_base64(JSON.stringify(params), 1, function (err, lzma) {
            if (err) {
                return done(err);
            }
            assert.response(server,
                {
                    url: layergroupUrl + '?lzma=' + encodeURIComponent(lzma),
                    method: 'GET',
                    headers: {
                        host: 'localhost'
                    },
                    encoding: 'binary'
                },
                {
                    status: 200
                },
                function (res) {
                    var parsed = JSON.parse(res.body);
                    assert.ok(parsed.layergroupid);

                    keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
                    keysToDelete['user:localhost:mapviews:global'] = 5;

                    done();
                }
            );
        });
    });

    it('creating a layergroup from lzma param, invalid json input', function (done) {
        var params = {
            config: 'WADUS'
        };

        testHelper.lzma_compress_to_base64(JSON.stringify(params), 1, function (err, lzma) {
            if (err) {
                return done(err);
            }
            assert.response(server,
                {
                    url: layergroupUrl + '?lzma=' + encodeURIComponent(lzma),
                    method: 'GET',
                    headers: {
                        host: 'localhost'
                    },
                    encoding: 'binary'
                },
                {
                    status: 400
                },
                function (res) {
                    var parsed = JSON.parse(res.body);
                    assert.ok(parsed.errors);
                    assert.strictEqual(parsed.errors.length, 1);
                    assert.ok(parsed.errors[0].match(/Unexpected token W/));

                    done();
                }
            );
        });
    });

    it('uses queries postgresql to figure affected tables in query', function (done) {
        var tableName = 'gadm4';
        var expectedCacheChannel = _.template('<%= databaseName %>:public.<%= tableName %>', {
            databaseName: _.template(global.environment.postgres_auth_user, { user_id: 1 }) + '_db',
            tableName: tableName
        });

        var layergroup = singleLayergroupConfig('select * from ' + tableName, '#gadm4 { marker-fill: red; }');

        assert.response(server,
            {
                url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
                method: 'GET',
                headers: {
                    host: 'localhost'
                }
            },
            {
                status: 200
            },
            function (res) {
                var parsed = JSON.parse(res.body);
                assert.ok(parsed.layergroupid);

                assert.ok(Object.prototype.hasOwnProperty.call(res.headers, 'x-cache-channel'));
                assert.strictEqual(res.headers['x-cache-channel'], expectedCacheChannel);

                assert.strictEqual(res.headers['x-layergroup-id'], parsed.layergroupid);

                keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
                keysToDelete['user:localhost:mapviews:global'] = 5;

                done();
            }
        );
    });

    // https://github.com/CartoDB/cartodb-postgresql/issues/86
    it.skip('should not fail with long table names because table name length limit', function (done) {
        var tableName = 'long_table_name_with_enough_chars_to_break_querytables_function';
        var expectedCacheChannel = _.template('<%= databaseName %>:public.<%= tableName %>', {
            databaseName: _.template(global.environment.postgres_auth_user, { user_id: 1 }) + '_db',
            tableName: tableName
        });

        var layergroup = singleLayergroupConfig('select * from ' + tableName, '#layer { marker-fill: red; }');

        assert.response(server,
            {
                url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
                method: 'GET',
                headers: {
                    host: 'localhost'
                }
            },
            {
                status: 200
            },
            function (res) {
                var parsed = JSON.parse(res.body);
                assert.ok(parsed.layergroupid);

                assert.ok(Object.prototype.hasOwnProperty.call(res.headers, 'x-cache-channel'));
                assert.strictEqual(res.headers['x-cache-channel'], expectedCacheChannel);

                assert.strictEqual(res.headers['x-layergroup-id'], parsed.layergroupid);

                done();
            }
        );
    });

    it('creates layergroup fails when postgresql queries fail to figure affected tables in query', function (done) {
        var runQueryFn = PgQueryRunner.prototype.run;
        PgQueryRunner.prototype.run = function (username, query, callback) {
            return callback(new Error('fake error message'), []);
        };

        var layergroup = singleLayergroupConfig('select * from gadm4', '#gadm4 { marker-fill: red; }');

        assert.response(server,
            {
                url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
                method: 'GET',
                headers: {
                    host: 'localhost'
                }
            },
            {
                status: 400
            },
            function (res) {
                PgQueryRunner.prototype.run = runQueryFn;

                assert.ok(!Object.prototype.hasOwnProperty.call(res.headers, 'x-cache-channel'));

                var parsed = JSON.parse(res.body);
                assert.deepStrictEqual(parsed.errors, ['fake error message']);

                done();
            }
        );
    });

    it('tile requests works when postgresql queries fail to figure affected tables in query', function (done) {
        var layergroup = singleLayergroupConfig('select * from gadm4', '#gadm4 { marker-fill: red; }');
        assert.response(server,
            {
                url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
                method: 'GET',
                headers: {
                    host: 'localhost'
                }
            },
            {
                status: 200
            },
            function (res) {
                keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0;
                keysToDelete['user:localhost:mapviews:global'] = 5;

                var affectedFn = QueryTables.getQueryMetadataModel;
                QueryTables.getQueryMetadataModel = function () {
                    return Promise.reject(new Error('fake error message'));
                };

                // reset internal cacheChannel cache
                // FIXME: we need a better way to reset cache while running tests
                server.layergroupAffectedTablesCache.cache.reset();

                assert.response(server,
                    {
                        url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
                            layergroupId: JSON.parse(res.body).layergroupid,
                            z: 0,
                            x: 0,
                            y: 0
                        }),
                        method: 'GET',
                        headers: {
                            host: 'localhost'
                        },
                        encoding: 'binary'
                    },
                    {
                        status: 200
                    },
                    function (res) {
                        QueryTables.getQueryMetadataModel = affectedFn;
                        assert.ok(!Object.prototype.hasOwnProperty.call(res.headers, 'x-cache-channel'));
                        done();
                    }
                );
            }
        );
    });
});
